Make some aspects of check/build available as an API.
authorNick Cameron <ncameron@mozilla.com>
Tue, 6 Dec 2016 03:15:46 +0000 (17:15 -1000)
committerNick Cameron <ncameron@mozilla.com>
Thu, 5 Jan 2017 02:58:58 +0000 (15:58 +1300)
There are two key parts to this commit:
* let API clients run `cargo check` with minimal fuss (ops/cargo_check.rs),
* let API clients intercept and customise Cargo's calls to rustc (all the Executor stuff).

src/bin/check.rs
src/cargo/core/shell.rs
src/cargo/ops/cargo_check.rs [new file with mode: 0644]
src/cargo/ops/cargo_compile.rs
src/cargo/ops/cargo_install.rs
src/cargo/ops/cargo_package.rs
src/cargo/ops/cargo_rustc/fingerprint.rs
src/cargo/ops/cargo_rustc/mod.rs
src/cargo/ops/mod.rs
src/cargo/util/config.rs
src/cargo/util/mod.rs

index 6d449f8d5aee40db9ae7c26aeb3f633e31c0f04f..8ea6747cd88f97c005f91120566294f400adf130 100644 (file)
@@ -1,32 +1,9 @@
 use std::env;
 
-use cargo::core::Workspace;
-use cargo::ops::{self, CompileOptions, MessageFormat};
-use cargo::util::important_paths::{find_root_manifest_for_wd};
+use cargo::ops::{self};
+use cargo::ops::cargo_check::{Options, with_check_env};
 use cargo::util::{CliResult, Config};
 
-#[derive(RustcDecodable)]
-pub struct Options {
-    flag_package: Vec<String>,
-    flag_jobs: Option<u32>,
-    flag_features: Vec<String>,
-    flag_all_features: bool,
-    flag_no_default_features: bool,
-    flag_target: Option<String>,
-    flag_manifest_path: Option<String>,
-    flag_verbose: u32,
-    flag_quiet: Option<bool>,
-    flag_color: Option<String>,
-    flag_message_format: MessageFormat,
-    flag_release: bool,
-    flag_lib: bool,
-    flag_bin: Vec<String>,
-    flag_example: Vec<String>,
-    flag_test: Vec<String>,
-    flag_bench: Vec<String>,
-    flag_locked: bool,
-    flag_frozen: bool,
-}
 
 pub const USAGE: &'static str = "
 Check a local package and all of its dependencies for errors
@@ -69,35 +46,9 @@ the --release flag will use the `release` profile instead.
 pub fn execute(options: Options, config: &Config) -> CliResult<Option<()>> {
     debug!("executing; cmd=cargo-check; args={:?}",
            env::args().collect::<Vec<_>>());
-    config.configure(options.flag_verbose,
-                     options.flag_quiet,
-                     &options.flag_color,
-                     options.flag_frozen,
-                     options.flag_locked)?;
-
-    let root = find_root_manifest_for_wd(options.flag_manifest_path, config.cwd())?;
-
-    let opts = CompileOptions {
-        config: config,
-        jobs: options.flag_jobs,
-        target: options.flag_target.as_ref().map(|t| &t[..]),
-        features: &options.flag_features,
-        all_features: options.flag_all_features,
-        no_default_features: options.flag_no_default_features,
-        spec: ops::Packages::Packages(&options.flag_package),
-        mode: ops::CompileMode::Check,
-        release: options.flag_release,
-        filter: ops::CompileFilter::new(options.flag_lib,
-                                        &options.flag_bin,
-                                        &options.flag_test,
-                                        &options.flag_example,
-                                        &options.flag_bench),
-        message_format: options.flag_message_format,
-        target_rustdoc_args: None,
-        target_rustc_args: None,
-    };
 
-    let ws = Workspace::new(&root, config)?;
-    ops::compile(&ws, &opts)?;
-    Ok(None)
+    with_check_env(options, config, |ws, opts| {
+        ops::compile(ws, opts)?;
+        Ok(None)
+    })
 }
index a28307874673285aebfff542544a3b629d53d955..58ed88f3f28174c8b8d50b673902171fd159020f 100644 (file)
@@ -62,6 +62,18 @@ impl MultiShell {
         MultiShell { out: out, err: err, verbosity: verbosity }
     }
 
+    // Create a quiet, basic shell from supplied writers.
+    pub fn from_write(out: Box<Write + Send>, err: Box<Write + Send>) -> MultiShell {
+        let config = ShellConfig { color_config: ColorConfig::Never, tty: false };
+        let out = Shell { terminal: NoColor(out), config: config.clone() };
+        let err = Shell { terminal: NoColor(err), config: config };
+        MultiShell {
+            out: out,
+            err: err,
+            verbosity: Verbosity::Quiet,
+        }
+    }
+
     pub fn out(&mut self) -> &mut Shell {
         &mut self.out
     }
diff --git a/src/cargo/ops/cargo_check.rs b/src/cargo/ops/cargo_check.rs
new file mode 100644 (file)
index 0000000..b8893ef
--- /dev/null
@@ -0,0 +1,89 @@
+use core::Workspace;
+use ops::{self, CompileOptions, MessageFormat};
+use util::important_paths::{find_root_manifest_for_wd};
+use util::{CliResult, Config};
+
+#[derive(RustcDecodable)]
+pub struct Options {
+    flag_package: Vec<String>,
+    flag_jobs: Option<u32>,
+    flag_features: Vec<String>,
+    flag_all_features: bool,
+    flag_no_default_features: bool,
+    flag_target: Option<String>,
+    flag_manifest_path: Option<String>,
+    flag_verbose: u32,
+    flag_quiet: Option<bool>,
+    flag_color: Option<String>,
+    flag_message_format: MessageFormat,
+    flag_release: bool,
+    flag_lib: bool,
+    flag_bin: Vec<String>,
+    flag_example: Vec<String>,
+    flag_test: Vec<String>,
+    flag_bench: Vec<String>,
+    flag_locked: bool,
+    flag_frozen: bool,
+}
+
+impl Options {
+    pub fn default() -> Options {
+        Options {
+            flag_package: vec![],
+            flag_jobs: None,
+            flag_features: vec![],
+            flag_all_features: false,
+            flag_no_default_features: false,
+            flag_target: None,
+            flag_manifest_path: None,
+            flag_verbose: 0,
+            flag_quiet: None,
+            flag_color: None,
+            flag_message_format: MessageFormat::Human,
+            flag_release: false,
+            flag_lib: false,
+            flag_bin: vec![],
+            flag_example: vec![],
+            flag_test: vec![],
+            flag_bench: vec![],
+            flag_locked: false,
+            flag_frozen: false,
+        }
+    }
+}
+
+
+pub fn with_check_env<F>(options: Options, config: &Config, f: F) -> CliResult<Option<()>>
+    where F: FnOnce(&Workspace, &CompileOptions) -> CliResult<Option<()>>
+{
+    config.configure(options.flag_verbose,
+                     options.flag_quiet,
+                     &options.flag_color,
+                     options.flag_frozen,
+                     options.flag_locked)?;
+
+    let root = find_root_manifest_for_wd(options.flag_manifest_path, config.cwd())?;
+
+    let opts = CompileOptions {
+        config: config,
+        jobs: options.flag_jobs,
+        target: options.flag_target.as_ref().map(|t| &t[..]),
+        features: &options.flag_features,
+        all_features: options.flag_all_features,
+        no_default_features: options.flag_no_default_features,
+        spec: ops::Packages::Packages(&options.flag_package),
+        mode: ops::CompileMode::Check,
+        release: options.flag_release,
+        filter: ops::CompileFilter::new(options.flag_lib,
+                                        &options.flag_bin,
+                                        &options.flag_test,
+                                        &options.flag_example,
+                                        &options.flag_bench),
+        message_format: options.flag_message_format,
+        target_rustdoc_args: None,
+        target_rustc_args: None,
+    };
+
+    let ws = Workspace::new(&root, config)?;
+    f(&ws, &opts)
+}
index 1be87408ab426ec8202ab58e188d337e8ca814cc..cce8fc2b64e1c673b7ac22274c3bccd65fce4fc5 100644 (file)
@@ -27,7 +27,7 @@ use std::path::PathBuf;
 
 use core::{Source, Package, Target};
 use core::{Profile, TargetKind, Profiles, Workspace, PackageIdSpec};
-use ops::{self, BuildOutput};
+use ops::{self, BuildOutput, Executor, DefaultExecutor};
 use util::config::Config;
 use util::{CargoResult, profile};
 
@@ -113,18 +113,26 @@ pub enum CompileFilter<'a> {
 
 pub fn compile<'a>(ws: &Workspace<'a>, options: &CompileOptions<'a>)
                    -> CargoResult<ops::Compilation<'a>> {
+    compile_with_exec(ws, options, &mut DefaultExecutor)
+}
+
+pub fn compile_with_exec<'a, E: Executor>(ws: &Workspace<'a>,
+                                          options: &CompileOptions<'a>, 
+                                          exec: &mut E)
+                                          -> CargoResult<ops::Compilation<'a>> {
     for member in ws.members() {
         for key in member.manifest().warnings().iter() {
             options.config.shell().warn(key)?
         }
     }
-    compile_ws(ws, None, options)
+    compile_ws(ws, None, options, exec)
 }
 
-pub fn compile_ws<'a>(ws: &Workspace<'a>,
-                      source: Option<Box<Source + 'a>>,
-                      options: &CompileOptions<'a>)
-                      -> CargoResult<ops::Compilation<'a>> {
+pub fn compile_ws<'a, E: Executor>(ws: &Workspace<'a>,
+                                   source: Option<Box<Source + 'a>>,
+                                   options: &CompileOptions<'a>,
+                                   exec: &mut E)
+                                   -> CargoResult<ops::Compilation<'a>> {
     let CompileOptions { config, jobs, target, spec, features,
                          all_features, no_default_features,
                          release, mode, message_format,
@@ -231,7 +239,8 @@ pub fn compile_ws<'a>(ws: &Workspace<'a>,
                              &resolve_with_overrides,
                              config,
                              build_config,
-                             profiles)?
+                             profiles,
+                             exec)?
     };
 
     ret.to_doc_test = to_builds.iter().map(|&p| p.clone()).collect();
index 5a144fc0f7a05725bb50dc733ac5db72f5bf693b..ce7285e21e52771f87ad9b9b07cf6da7dcccb7fa 100644 (file)
@@ -13,7 +13,7 @@ use toml;
 
 use core::{SourceId, Source, Package, Dependency, PackageIdSpec};
 use core::{PackageId, Workspace};
-use ops::{self, CompileFilter};
+use ops::{self, CompileFilter, DefaultExecutor};
 use sources::{GitSource, PathSource, SourceConfigMap};
 use util::{CargoResult, ChainError, Config, human, internal};
 use util::{Filesystem, FileLock};
@@ -106,7 +106,7 @@ pub fn install(root: Option<&str>,
         check_overwrites(&dst, pkg, &opts.filter, &list, force)?;
     }
 
-    let compile = ops::compile_ws(&ws, Some(source), opts).chain_error(|| {
+    let compile = ops::compile_ws(&ws, Some(source), opts, &mut DefaultExecutor).chain_error(|| {
         if let Some(td) = td_opt.take() {
             // preserve the temporary directory, so the user can inspect it
             td.into_path();
index e27646d9f8f12143ec4f67602ff6f190afd1ccde..8436fc3806a4856dcb67fd6888c8bda3c956c25b 100644 (file)
@@ -11,7 +11,7 @@ use tar::{Archive, Builder, Header};
 use core::{SourceId, Package, PackageId, Workspace, Source};
 use sources::PathSource;
 use util::{self, CargoResult, human, internal, ChainError, Config, FileLock};
-use ops;
+use ops::{self, DefaultExecutor};
 
 pub struct PackageOpts<'cfg> {
     pub config: &'cfg Config,
@@ -298,7 +298,7 @@ fn run_verify(ws: &Workspace, tar: &File, opts: &PackageOpts) -> CargoResult<()>
         mode: ops::CompileMode::Build,
         target_rustdoc_args: None,
         target_rustc_args: None,
-    })?;
+    }, &mut DefaultExecutor)?;
 
     Ok(())
 }
index 3d1f274498f3a443573414f861b53ab7a3c26a1e..dde13d4c2a7ea5a03aae8f2fc9e4af6e41721b7c 100644 (file)
@@ -148,11 +148,10 @@ impl Fingerprint {
     fn update_local(&self) -> CargoResult<()> {
         match self.local {
             LocalFingerprint::MtimeBased(ref slot, ref path) => {
-                let meta = fs::metadata(path).chain_error(|| {
-                    internal(format!("failed to stat `{}`", path.display()))
-                })?;
-                let mtime = FileTime::from_last_modification_time(&meta);
-                *slot.0.lock().unwrap() = Some(mtime);
+                if let Ok(meta) = fs::metadata(path) {
+                    let mtime = FileTime::from_last_modification_time(&meta);
+                    *slot.0.lock().unwrap() = Some(mtime);
+                }
             }
             LocalFingerprint::Precalculated(..) => return Ok(())
         }
index 24c3de65697ace7eca423bb0e9058bbe8829c8d9..23107efabf8babe43ae45d7353f94a821ab2b27e 100644 (file)
@@ -11,7 +11,7 @@ use rustc_serialize::json;
 use core::{Package, PackageId, PackageSet, Target, Resolve};
 use core::{Profile, Profiles, Workspace};
 use core::shell::ColorConfig;
-use util::{self, CargoResult, ProcessBuilder, human, machine_message};
+use util::{self, CargoResult, ProcessBuilder, ProcessError, human, machine_message};
 use util::{Config, internal, ChainError, profile, join_paths, short_hash};
 
 use self::job::{Job, Work};
@@ -55,16 +55,47 @@ pub struct TargetConfig {
 
 pub type PackagesToBuild<'a> = [(&'a Package, Vec<(&'a Target, &'a Profile)>)];
 
+/// A glorified callback for executing calls to rustc. Rather than calling rustc
+/// directly, we'll use an Executor, giving clients an opportunity to intercept
+/// the build calls.
+pub trait Executor: Clone + Send + 'static {
+    fn init(&mut self, cx: &Context);
+    /// If execution succeeds, the ContinueBuild value indicates whether Cargo
+    /// should continue with the build process for this package.
+    fn exec(&self, cmd: ProcessBuilder, id: &PackageId) -> Result<ContinueBuild, ProcessError>;
+}
+
+/// A DefaultExecutorcalls rustc without doing anything else. It is Cargo's
+/// default behaviour.
+#[derive(Copy, Clone)]
+pub struct DefaultExecutor;
+
+impl Executor for DefaultExecutor {
+    fn init(&mut self, _cx: &Context) {}
+
+    fn exec(&self, cmd: ProcessBuilder, _id: &PackageId) -> Result<ContinueBuild, ProcessError> {
+        cmd.exec()?;
+        Ok(ContinueBuild::Continue)
+    }
+}
+
+#[derive(Debug, Clone, Copy, PartialEq, Eq)]
+pub enum ContinueBuild {
+    Continue,
+    Stop,
+}
+
 // Returns a mapping of the root package plus its immediate dependencies to
 // where the compiled libraries are all located.
-pub fn compile_targets<'a, 'cfg: 'a>(ws: &Workspace<'cfg>,
-                                     pkg_targets: &'a PackagesToBuild<'a>,
-                                     packages: &'a PackageSet<'cfg>,
-                                     resolve: &'a Resolve,
-                                     config: &'cfg Config,
-                                     build_config: BuildConfig,
-                                     profiles: &'a Profiles)
-                                     -> CargoResult<Compilation<'cfg>> {
+pub fn compile_targets<'a, 'cfg: 'a, E: Executor>(ws: &Workspace<'cfg>,
+                                                  pkg_targets: &'a PackagesToBuild<'a>,
+                                                  packages: &'a PackageSet<'cfg>,
+                                                  resolve: &'a Resolve,
+                                                  config: &'cfg Config,
+                                                  build_config: BuildConfig,
+                                                  profiles: &'a Profiles, 
+                                                  exec: &mut E)
+                                                  -> CargoResult<Compilation<'cfg>> {
     let units = pkg_targets.iter().flat_map(|&(pkg, ref targets)| {
         let default_kind = if build_config.requested_target.is_some() {
             Kind::Target
@@ -97,7 +128,7 @@ pub fn compile_targets<'a, 'cfg: 'a>(ws: &Workspace<'cfg>,
         // part of this, that's all done next as part of the `execute`
         // function which will run everything in order with proper
         // parallelism.
-        compile(&mut cx, &mut queue, unit)?;
+        compile(&mut cx, &mut queue, unit, exec)?;
     }
 
     // Now that we've figured out everything that we're going to do, do it!
@@ -167,9 +198,10 @@ pub fn compile_targets<'a, 'cfg: 'a>(ws: &Workspace<'cfg>,
     Ok(cx.compilation)
 }
 
-fn compile<'a, 'cfg: 'a>(cx: &mut Context<'a, 'cfg>,
-                         jobs: &mut JobQueue<'a>,
-                         unit: &Unit<'a>) -> CargoResult<()> {
+fn compile<'a, 'cfg: 'a, E: Executor>(cx: &mut Context<'a, 'cfg>,
+                                      jobs: &mut JobQueue<'a>,
+                                      unit: &Unit<'a>,
+                                      exec: &mut E) -> CargoResult<()> {
     if !cx.compiled.insert(*unit) {
         return Ok(())
     }
@@ -188,7 +220,7 @@ fn compile<'a, 'cfg: 'a>(cx: &mut Context<'a, 'cfg>,
         let work = if unit.profile.doc {
             rustdoc(cx, unit)?
         } else {
-            rustc(cx, unit)?
+            rustc(cx, unit, exec)?
         };
         let link_work1 = link_targets(cx, unit)?;
         let link_work2 = link_targets(cx, unit)?;
@@ -202,13 +234,13 @@ fn compile<'a, 'cfg: 'a>(cx: &mut Context<'a, 'cfg>,
 
     // Be sure to compile all dependencies of this target as well.
     for unit in cx.dep_targets(unit)?.iter() {
-        compile(cx, jobs, unit)?;
+        compile(cx, jobs, unit, exec)?;
     }
 
     Ok(())
 }
 
-fn rustc(cx: &mut Context, unit: &Unit) -> CargoResult<Work> {
+fn rustc<E: Executor>(cx: &mut Context, unit: &Unit, exec: &mut E) -> CargoResult<Work> {
     let crate_types = unit.target.rustc_crate_types();
     let mut rustc = prepare_rustc(cx, crate_types, unit)?;
 
@@ -257,6 +289,10 @@ fn rustc(cx: &mut Context, unit: &Unit) -> CargoResult<Work> {
                      .flat_map(|i| i)
                      .map(|s| s.to_string())
                      .collect::<Vec<_>>();
+
+    exec.init(cx);
+    let exec = exec.clone();
+
     return Ok(Work::new(move |state| {
         // Only at runtime have we discovered what the extra -L and -l
         // arguments are for native libraries, so we process those here. We
@@ -290,7 +326,7 @@ fn rustc(cx: &mut Context, unit: &Unit) -> CargoResult<Work> {
         }
 
         state.running(&rustc);
-        if json_messages {
+        let cont = if json_messages {
             rustc.exec_with_streaming(
                 &mut |line| if !line.is_empty() {
                     Err(internal(&format!("compiler stdout is not empty: `{}`", line)))
@@ -316,32 +352,37 @@ fn rustc(cx: &mut Context, unit: &Unit) -> CargoResult<Work> {
                     }
                     Ok(())
                 },
-            ).map(|_| ())
+            ).map(|_| ()).chain_error(|| {
+                human(format!("Could not compile `{}`.", name))
+            })?;
+            ContinueBuild::Continue
         } else {
-            rustc.exec()
-        }.chain_error(|| {
-            human(format!("Could not compile `{}`.", name))
-        })?;
+            exec.exec(rustc, &package_id).chain_error(|| {
+                human(format!("Could not compile `{}`.", name))
+            })?
+        };
 
-        if do_rename && real_name != crate_name {
-            let dst = &filenames[0].0;
-            let src = dst.with_file_name(dst.file_name().unwrap()
-                                            .to_str().unwrap()
-                                            .replace(&real_name, &crate_name));
-            if !has_custom_args || src.exists() {
-                fs::rename(&src, &dst).chain_error(|| {
-                    internal(format!("could not rename crate {:?}", src))
-                })?;
+        if cont == ContinueBuild::Continue {
+            if do_rename && real_name != crate_name {
+                let dst = &filenames[0].0;
+                let src = dst.with_file_name(dst.file_name().unwrap()
+                                                .to_str().unwrap()
+                                                .replace(&real_name, &crate_name));
+                if !has_custom_args || src.exists() {
+                    fs::rename(&src, &dst).chain_error(|| {
+                        internal(format!("could not rename crate {:?}", src))
+                    })?;
+                }
             }
-        }
 
-        if !has_custom_args || fs::metadata(&rustc_dep_info_loc).is_ok() {
-            info!("Renaming dep_info {:?} to {:?}", rustc_dep_info_loc, dep_info_loc);
-            fs::rename(&rustc_dep_info_loc, &dep_info_loc).chain_error(|| {
-                internal(format!("could not rename dep info: {:?}",
-                              rustc_dep_info_loc))
-            })?;
-            fingerprint::append_current_dir(&dep_info_loc, &cwd)?;
+            if !has_custom_args || fs::metadata(&rustc_dep_info_loc).is_ok() {
+                info!("Renaming dep_info {:?} to {:?}", rustc_dep_info_loc, dep_info_loc);
+                fs::rename(&rustc_dep_info_loc, &dep_info_loc).chain_error(|| {
+                    internal(format!("could not rename dep info: {:?}",
+                                  rustc_dep_info_loc))
+                })?;
+                fingerprint::append_current_dir(&dep_info_loc, &cwd)?;
+            }
         }
 
         if json_messages {
index 865b740948c950aa9e1d301904ae15295a52ad24..0b59fb923008563a1a0a793edd3ea3e85400fb3c 100644 (file)
@@ -1,10 +1,11 @@
 pub use self::cargo_clean::{clean, CleanOptions};
-pub use self::cargo_compile::{compile, compile_ws, CompileOptions};
+pub use self::cargo_compile::{compile, compile_with_exec, compile_ws, CompileOptions};
 pub use self::cargo_compile::{CompileFilter, CompileMode, MessageFormat, Packages};
 pub use self::cargo_read_manifest::{read_manifest,read_package,read_packages};
 pub use self::cargo_rustc::{compile_targets, Compilation, Kind, Unit};
 pub use self::cargo_rustc::Context;
 pub use self::cargo_rustc::{BuildOutput, BuildConfig, TargetConfig};
+pub use self::cargo_rustc::{Executor, DefaultExecutor, ContinueBuild};
 pub use self::cargo_run::run;
 pub use self::cargo_install::{install, install_list, uninstall};
 pub use self::cargo_new::{new, init, NewOptions, VersionControl};
@@ -23,6 +24,7 @@ pub use self::cargo_pkgid::pkgid;
 pub use self::resolve::{resolve_ws, resolve_ws_precisely, resolve_with_previous};
 pub use self::cargo_output_metadata::{output_metadata, OutputMetadataOptions, ExportInfo};
 
+pub mod cargo_check;
 mod cargo_clean;
 mod cargo_compile;
 mod cargo_doc;
index 598a6dcccbedaae61d9dce3469061c8884608854..63382c10cb9f8a5aef16fdfe61dc08209750b3e0 100644 (file)
@@ -47,7 +47,7 @@ impl Config {
             rustdoc: LazyCell::new(),
             extra_verbose: Cell::new(false),
             frozen: Cell::new(false),
-            locked: Cell::new(false),
+            locked: Cell::new(false),   
         }
     }
 
@@ -635,7 +635,7 @@ impl fmt::Display for Definition {
     }
 }
 
-fn homedir(cwd: &Path) -> Option<PathBuf> {
+pub fn homedir(cwd: &Path) -> Option<PathBuf> {
     let cargo_home = env::var_os("CARGO_HOME").map(|home| {
         cwd.join(home)
     });
index d1195d75cf89cabc100db30840ebb8d82164dfb2..0f9e92051b76aee96d11b9cb6e863cf4e6560fec 100644 (file)
@@ -1,5 +1,5 @@
 pub use self::cfg::{Cfg, CfgExpr};
-pub use self::config::Config;
+pub use self::config::{Config, homedir};
 pub use self::dependency_queue::{DependencyQueue, Fresh, Dirty, Freshness};
 pub use self::errors::{CargoResult, CargoError, ChainError, CliResult};
 pub use self::errors::{CliError, ProcessError, CargoTestError};